// Copyright (c) 2023 CHANGLEI. All rights reserved.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

/// ANSI Control Sequence Introducer, signals the terminal for new settings.
const ansiEscape = '\x1B[';

/// Reset all colors and options for current SGRs to terminal defaults.
const ansiDefault = '${ansiEscape}0m';

const _supportsAnsiEscapes = true;

/// Created by changlei on 2023/5/22.
/// Pen attributes for foreground and background colors.
///
/// Use the pen in string interpolation to output ansi codes.
/// Use [trailing] in string interpolation to globally reset colors.
final class AnsiColor {
  /// Pen attributes for foreground and background colors.
  const AnsiColor({int? foreground, int? background, bool? enabled})
      : _color = (foreground: foreground, background: background),
        _enabled = enabled;

  /// Pen attributes for foreground and background colors.
  const AnsiColor.foreground(int? foreground, {bool? enabled})
      : _color = (foreground: foreground, background: null),
        _enabled = enabled;

  /// Pen attributes for foreground and background colors.
  const AnsiColor.background(int? background, {bool? enabled})
      : _color = (foreground: null, background: background),
        _enabled = enabled;

  final ({int? foreground, int? background}) _color;
  final bool? _enabled;

  /// foreground
  int? get foreground => _color.foreground;

  /// background
  int? get background => _color.background;

  /// Globally enable or disable [AnsiColor] settings.
  ///
  /// Note: defaults to environment support; but can be overridden.
  ///
  /// Handy for turning on and off embedded colors without commenting out code.
  bool get enabled => _enabled ?? (_supportsAnsiEscapes || stdout.supportsAnsiEscapes);

  /// Returns control codes to change the terminal colors.
  String get leading => enabled ? _color.leading : '';

  /// Resets all pen attributes in the terminal.
  String get trailing => enabled ? _color.trailing : '';

  /// Write the [msg.toString()] with the pen's current settings and then
  /// reset all attributes.
  String wrap(Object? msg) => '$leading$msg$trailing';

  /// Write the [msg.toString()] with the pen's current settings and then
  /// reset all attributes.
  String forceWrap(Object? msg) => '${_color.leading}$msg${_color.trailing}';

  /// copy
  AnsiColor copyWith({
    int? foreground,
    int? background,
    bool? enabled,
  }) {
    return AnsiColor(
      foreground: foreground ?? this.foreground,
      background: background ?? this.background,
      enabled: enabled ?? _enabled,
    );
  }

  String call(Object? msg) => wrap(msg);
}

extension on int {
  int get parsed {
    final value = this & 0xFFFFFFFF;
    assert(((0xff000000 & value) >> 24) == 255);
    final r = ((0x00ff0000 & value) >> 16) / 255;
    final g = ((0x0000ff00 & value) >> 8) / 255;
    final b = ((0x000000ff & value) >> 0) / 255;
    return parseRgb(r, g, b);
  }

  static int parseRgb(double r, double g, double b) {
    final red = (r.clamp(0.0, 1.0) * 5).toInt() * 36;
    final green = (g.clamp(0.0, 1.0) * 5).toInt() * 6;
    final blue = (b.clamp(0.0, 1.0) * 5).toInt() + 16;
    return (red + green + blue).clamp(0, 255);
  }
}

extension on ({int? foreground, int? background}) {
  String get leading {
    final sb = StringBuffer();
    final foreground = this.foreground;
    if (foreground != null) {
      sb.write('${ansiEscape}38;5;${foreground.parsed}m');
    }

    final background = this.background;
    if (background != null) {
      sb.write('${ansiEscape}48;5;${background.parsed}m');
    }
    return sb.toString();
  }

  String get trailing {
    return foreground == null && background == null ? '' : ansiDefault;
  }
}

/// 日志级别
enum Level implements Comparable<Level> {
  /// Key for normal information ([value] = 0).
  normal(
    value: 0,
    textColor: AnsiColor.foreground(0),
  ),

  /// Key for tracing information ([value] = 500).
  verbose(
    value: 500,
    textColor: AnsiColor.foreground(0xffbbbbbb),
  ),

  /// Key for static configuration messages ([value] = 700).
  debug(
    value: 700,
    textColor: AnsiColor.foreground(0xff299999),
  ),

  /// Key for informational messages ([value] = 800).
  info(
    value: 800,
    textColor: AnsiColor.foreground(0xff39af3c),
  ),

  /// Key for potential problems ([value] = 900).
  warn(
    value: 900,
    textColor: AnsiColor.foreground(0xffbbb529),
  ),

  /// Key for serious failures ([value] = 1000).
  error(
    value: 1000,
    textColor: AnsiColor.foreground(0xffff6b68),
  ),

  /// Key for extra debugging loudness ([value] = 1200).
  shout(
    value: 1200,
    textColor: AnsiColor.foreground(0xffc0332f),
  );

  /// 日志级别
  const Level({
    required this.value,
    required this.textColor,
  });

  /// Unique value for this level. Used to order levels, so filtering can
  /// exclude messages whose level is under certain value.
  final int value;

  /// 文本颜色
  final AnsiColor textColor;

  /// 别名
  String get alias => this == shout ? r'assert' : name;

  @override
  int compareTo(Level other) => value - other.value;

  @override
  String toString() => alias;

  String call(Object? msg) {
    var string = msg.toString();
    if (this == normal) {
      return string;
    }
    string = string.split('\n').map(textColor.call).join('\n');
    return string;
  }

  void write(Object? object) {
    stdout.write(call(object));
  }

  void rewrite(Object? object) {
    write('\r');
    write(object);
  }

  void writeln([Object? object = r'']) {
    stdout.writeln(call(object));
  }

  void writeAll(Iterable<dynamic> objects, [String sep = r'']) {
    stdout.writeAll(objects.map(call), sep);
  }

  void add(List<int> data) {
    write(String.fromCharCodes(data));
  }

  void addStream(Stream<List<int>> stream) {
    stdout.addStream(
      stream.transform(
        StreamTransformer.fromBind((stream) {
          return Stream<List<int>>.eventTransformed(stream, (sink) {
            return _AnsiColorSink(sink, this);
          });
        }),
      ),
    );
  }
}

final class _AnsiColorSink implements EventSink<List<int>> {
  const _AnsiColorSink(this.sink, this.level);

  final EventSink<List<int>> sink;
  final Level level;

  @override
  void add(List<int> event) {
    sink.add(utf8.encode(level(utf8.decode(event))));
  }

  @override
  void addError(Object error, [StackTrace? stackTrace]) {
    sink.addError(error, stackTrace);
  }

  @override
  void close() {
    sink.close();
  }
}
